<!DOCTYPE html>
<html lang="zh">

<head>
  <title>缠论解盘 - TradingView Chart</title>

  <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
  <link rel="stylesheet" href="{{ url_for('static', filename='css/layui.css') }}" />
  <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />

  <script type="text/javascript" src="{{ url_for('static', filename='jquery-3.7.0.min.js') }}"></script>
  <script type="text/javascript" src="{{ url_for('static', filename='layui.js') }}"></script>
  <script type="text/javascript" src="{{ url_for('static', filename='xm-select.js') }}"></script>
  <script type="text/javascript"
    src="{{ url_for('static', filename='charting_library/charting_library.standalone.js') }}"></script>
  <script type="text/javascript" src="{{ url_for('static', filename='datafeeds/udf/dist/bundle.js') }}"></script>
  {% include 'dark.html' %}
</head>

<body>
  <div class="layui-fluid" style="padding: 0">
    <div class="layui-row">
      <div class="layui-col-xs10 layui-col-sm10 layui-col-md10" id="chart_container"
        style="display: flex; flex-direction: column; height: 100vh">
        {# 图表内容 #}
        <div id="tv_chart_container_1" style="flex: 0.5"></div>
        <div id="tv_chart_container_2" style="flex: 0.5"></div>
      </div>
      <div class="layui-col-xs2 layui-col-sm2 layui-col-md2" id="chart_menu" style="
            position: fixed;
            top: 0;
            bottom: 0;
            right: 0;
            overflow: scroll;
            padding: 0;
            margin: 0;
          ">
        <div style="padding: 10px">
          <div class="layui-inline" style="width: 100%">
            <h2 style="float: left; margin-top: 9px">缠论解盘</h2>
            <div id="toggle-menu" style="float: right; margin-top: 10px; cursor: pointer">
              <div>
                <i class="layui-icon layui-icon-shrink-right"></i>
              </div>
            </div>
          </div>
          <hr class="layui-border-red" />
          <form class="layui-form layui-form-pane" lay-filter="select_market_form">
            <div class="layui-form-item">
              <div class="layui-input-block" style="margin: 0; padding: 0">
                <label>
                  <select name="market" lay-filter="select_market">
                    <option value="a">沪深A股</option>
                    <option value="hk">港股</option>
                    <option value="futures">期货</option>
                    <option value="fx">外汇</option>
                    <option value="us">美股</option>
                    <option value="currency">数字货币(合约)</option>
                    <option value="currency_spot">数字货币(现货)</option>
                  </select>
                </label>
              </div>
            </div>
            <div class="layui-form-item">
              <div id="code_search" class="xm-select-demo"></div>
            </div>
          </form>
        </div>
        <div class="layui-collapse lay-accordion" lay-filter="collapse-opts">
          <div class="layui-colla-item">
            {# 自选内容 #}
            <div class="layui-colla-title" data-ca-title="自选组">自选组</div>
            <div class="layui-colla-content layui-show" style="padding: 0">
              <form class="layui-form" style="width: 100%">
                <div class="layui-form-item">
                  <div class="layui-inline">
                    <div class="layui-input-inline layui-input-wrap" style="width: 60%; float: left">
                      <label for="zixuan_groups">
                        <select id="zixuan_groups" name="zixuan_groups" lay-filter="select_zx_group"></select>
                      </label>
                    </div>
                    <div class="layui-form-mid" style="padding: 4px 0 !important; float: left">
                      <button type="button" id="add_zixuan"
                        class="layui-btn layui-btn-primary layui-border-red layui-btn-sm">
                        <i class="layui-icon layui-icon-add-1"></i> 自选
                      </button>
                    </div>
                    <div class="layui-form-mid" style="padding: 4px 0 !important; float: left">
                      <button type="button" id="refresh_zixuan"
                        class="layui-btn layui-btn-primary layui-border-red layui-btn-sm">
                        <i class="layui-icon layui-icon-refresh"></i>
                      </button>
                    </div>
                  </div>
                </div>
              </form>
              <table class="layui-hide" id="table_zixuan_list"></table>
              <hr class="layui-border-red" />
            </div>
          </div>
          <div class="layui-colla-item">
            <div class="layui-colla-title" data-ca-title="监控提醒">
              监控提醒
            </div>
            <div class="layui-colla-content" style="padding: 0">
              <table class="layui-hide" id="table_alert_reocrds" lay-filter="table_alert_reocrds"></table>

              <hr class="layui-border-red" />
            </div>
          </div>
          <div class="layui-colla-item">
            <div class="layui-colla-title" data-ca-title="AI分析助手">
              AI分析助手
            </div>
            <div class="layui-colla-content" style="padding: 0">
              <form class="layui-form" style="width: 100%">
                <div class="layui-form-item">
                  <div class="layui-inline">
                    <div class="layui-input-inline layui-input-wrap" style="width: 40%; float: left">
                      <label for="ai_code">
                        <input type="text" name="ai_code" id="ai_code" autocomplete="off" readonly="readonly"
                          class="layui-input" />
                      </label>
                    </div>
                    <div class="layui-form-mid" style="width: 30%;padding: 0 !important; float: left">
                      <label>
                        <select name="ai_frequencys" id="ai_frequencys">

                        </select>
                      </label>
                    </div>
                    <div class="layui-form-mid" style="padding: 4px 0 !important; float: left">
                      <button type="button" id="ai_analyse_btn"
                        class="layui-btn layui-btn-primary layui-border-red layui-btn-sm">
                        分析
                      </button>
                    </div>
                  </div>
                </div>
              </form>
              <table class="layui-hide" id="table_ai_analysis" lay-filter="table_ai_analysis"></table>

              <hr class="layui-border-red" />
            </div>
          </div>
          <div class="layui-colla-item">
            <div class="layui-colla-title" data-ca-title="关于">关于</div>
            <div class="layui-colla-content" style="padding: 0">
              <div class="layui-text" style="padding: 16px">
                <h3>Chanlun-Pro</h3>
                <p>基于缠论的市场行情量化分析工具</p>
                <p>
                  <a href="https://github.com/yijixiuxin/chanlun-pro" target="_blank">Github 地址</a>
                  <a href="https://gitee.com/wang-student/chanlun-pro" target="_blank">Gitee 地址</a>
                </p>
                <p>
                  项目文档：<a href="https://chanlun-pro.readthedocs.io/" target="_blank">链接</a>
                </p>
                <p>
                  项目作者<br />
                  <img src="{{ url_for('static', filename='imgs/wx.jpg') }}" width="200px" height="260px" alt="" />
                </p>
              </div>

              <hr class="layui-border-red" />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  {% include 'sub_temp.html' %}
</body>
<script>
  // 定时更新tick涨跌幅的对象
  var interval_update_rates;
  var interval_get_alert_records;

  var market_frequencys = {{ market_frequencys| tojson }};

  $(function () {
    // 获取本地存储的数据
    function get_local_data(key) {
      let default_vals = {
        theme: "Light",
        market: "a",
        a_interval_1: "1D",
        a_interval_2: "1D",
        hk_interval_1: "1D",
        hk_interval_2: "1D",
        us_interval_1: "1D",
        us_interval_2: "1D",
        fx_interval_1: "1D",
        fx_interval_2: "1D",
        futures_interval_1: "1D",
        futures_interval_2: "1D",
        currency_interval_1: "1D",
        currency_interval_2: "1D",
        currency_spot_interval_1: "1D",
        currency_spot_interval_2: "1D",
        chart_num: "1",
        a_code: "{{ market_default_codes.a }}",
        hk_code: "{{ market_default_codes.hk }}",
        us_code: "{{ market_default_codes.us }}",
        fx_code: "{{ market_default_codes.fx }}",
        futures_code: "{{ market_default_codes.futures }}",
        currency_code: "{{ market_default_codes.currency }}",
        currency_spot_code: "{{ market_default_codes.currency_spot }}",
      };

      if (layui.data("tv_chart")) {
        let val = layui.data("tv_chart")[key];
        if (val === undefined) {
          return default_vals[key];
        }
        return val;
      }
      return default_vals[key];
    }

    // 设置本地存储 数据
    function set_local_data(key, val) {
      layui.data("tv_chart", { key: key, value: val });
      return true;
    }

    var market = get_local_data("market");
    var frequencys = market_frequencys[market];
    var zx_group = "我的关注";
    var code = get_local_data(market + "_code");

    var chart_widgets = [];

    // 图表展示
    function show_tv_chart(id) {
      var widget, udf_datafeed;
      var obj_charts = {};

      // 手机版禁用的功能
      // let device = layui.device()
      let disabled_features = ["go_to_date"];
      // if (device.mobile) {
      //     disabled_features = [
      //         'use_localstorage_for_settings',
      //         'left_toolbar', 'header_widget', 'timeframes_toolbar', 'edit_buttons_in_legend',
      //         'context_menus', 'control_bar', 'border_around_the_chart'
      //     ];
      // }

      udf_datafeed = new Datafeeds.UDFCompatibleDatafeed("/tv", 30000);
      widget = window.tvWidget = new TradingView.widget({
        // debug: true, // uncomment this line to see Library errors and warnings in the console
        autosize: true,

        symbol: market + ":" + code,
        interval: get_local_data(market + "_interval_" + id),
        container: "tv_chart_container_" + id,
        debug: false,

        //	BEWARE: no trailing slash is expected in feed URL
        datafeed: udf_datafeed,
        library_path: "static/charting_library/",
        locale: "zh",

        disabled_features: disabled_features,
        enabled_features: ["study_templates", "seconds_resolution"],

        theme: get_local_data("theme"),
        numeric_formatting: { decimal_sign: "." },
        time_frames: [],
        timezone: "Asia/Shanghai",

        symbol_search_request_delay: 100,
        auto_save_delay: 5,
        study_count_limit: 100,

        saved_data_meta_info: {
          uid: 1,
          name: "default",
          description: "default",
        },

        charts_storage_url: "/tv",
        charts_storage_api_version: "1.1",
        client_id: "chanlun_pro_" + id,
        user_id: "999",
        load_last_chart: true,

        custom_indicators_getter: function (PineJS) {
          return Promise.resolve([
            {
              name: 'Custom Indicators DEMO',
              metainfo: {
                _metainfoVersion: 53,
                id: 'CustomIndicatorsDemo@tv-basicstudies-1',
                description: '自定义指标示例',
                shortDescription: '自定义指标示例',
                is_price_study: true,
                isCustomIndicator: true,
                plots: [
                  {
                    'id': 'plot_0',
                    'type': 'line',
                  },
                ],
                palettes: {
                  paletteId1: {
                    colors: {
                      0: {
                        name: 'First color',
                      },
                    },
                  },
                },
                defaults: {
                  palettes: {
                    paletteId1: {
                      colors: {
                        0: {
                          color: 'red',
                          width: 1,
                          style: 0,
                        },
                      },
                    },
                  },
                  styles: {},
                  precision: 4,
                  inputs: {},
                },
                styles: {
                  plot_0: {
                    title: 'Equity value',
                    histogramBase: 0,
                  },
                },
                inputs: [],
                format: {
                  type: 'price',
                  precision: 4,
                },
              },
              constructor: function () {
                this.init = function () {
                  this._highs = [];
                };
                this.main = function (context, inputCallback) {
                  this._context = context;
                  this._input = inputCallback;

                  const h = this._context.new_var(PineJS.Std.high(this._context));
                  const high_val = PineJS.Std.highest(h, 20, this._context);
                  console.log(high_val);

                  return [high_val];
                };
              }
            },
            // kdj的自定义指标
            {
              name: 'Custom KDJ',
              metainfo: {
                _metainfoVersion: 53,
                id: 'CustomIndicatorsKDJ@tv-basicstudies-1',
                description: 'KDJ',
                shortDescription: 'KDJ 随机指标',
                is_price_study: false,
                isCustomIndicator: true,
                plots: [
                  {
                    'id': 'plot_k',
                    'type': 'line',
                  },
                  {
                    'id': 'plot_d',
                    'type': 'line',
                  },
                  {
                    'id': 'plot_j',
                    'type': 'line',
                  },
                ],
                defaults: {
                  palettes: {
                  },
                  styles: {
                    plot_k: {
                      linestyle: 0,
                      linewidth: 1,
                      plottype: 0,
                      trackPrice: false,
                      transparency: 0,
                      visible: true,
                      color: '#FFCC33',
                    },
                    plot_d: {
                      linestyle: 0,
                      linewidth: 1,
                      plottype: 0,
                      trackPrice: false,
                      transparency: 0,
                      visible: true,
                      color: '#33CCFF',
                    },
                    plot_j: {
                      linestyle: 0,
                      linewidth: 1,
                      plottype: 0,
                      trackPrice: false,
                      transparency: 0,
                      visible: true,
                      color: '#FF3366',
                    },
                  },
                  inputs: {
                    N: 9,
                    M1: 3,
                    M2: 3,
                  },
                },
                palettes: {},
                styles: {
                  plot_k: {
                    title: 'K',
                    histogramBase: 0,
                  },
                  plot_d: {
                    title: 'D',
                    histogramBase: 0,
                  },
                  plot_j: {
                    title: 'J',
                    histogramBase: 0,
                  },
                },
                inputs: [
                  {
                    id: 'N',
                    name: 'N',
                    type: 'integer',
                    defval: 9,
                    min: 1,
                    max: 100,
                  },
                  {
                    id: 'M1',
                    name: 'M1',
                    type: 'integer',
                    defval: 3,
                    min: 1,
                    max: 100,
                  },
                  {
                    id: 'M2',
                    name: 'M2',
                    type: 'integer',
                    defval: 3,
                    min: 1,
                    max: 100,
                  },
                ],
                format: {},
              },
              constructor: function () {
                this.init = function (context, inputCallback) {
                };
                this.main = function (context, inputCallback) {
                  this._context = context;
                  this._input = inputCallback;

                  var N = this._input(0);
                  var M1 = this._input(1);
                  var M2 = this._input(2);

                  const h = this._context.new_var(PineJS.Std.high(this._context));
                  const l = this._context.new_var(PineJS.Std.low(this._context));
                  const c = this._context.new_var(PineJS.Std.close(this._context));

                  const hh = PineJS.Std.highest(h, N, this._context);
                  const ll = PineJS.Std.lowest(l, N, this._context);
                  const rsv = this._context.new_var((c - ll) / (hh - ll) * 100);

                  const k = this._context.new_var(PineJS.Std.ema(rsv, (M1 * 2 - 1), this._context));
                  const d = this._context.new_var(PineJS.Std.ema(k, (M2 * 2 - 1), this._context));
                  const j = this._context.new_var(3 * k - 2 * d);

                  return [k.get(0), d.get(0), j.get(0)];
                };
              }
            },
            // ama的自定义指标
            {
              name: 'Custom AMA',
              metainfo: {
                _metainfoVersion: 53,
                id: 'CustomIndicatorsAMA@tv-basicstudies-1',
                description: 'AMA',
                shortDescription: 'AMA 自适应移动均线',
                is_price_study: true,
                isCustomIndicator: true,
                plots: [
                  {
                    'id': 'plot_ama',
                    'type': 'line',
                  },
                  {
                    'id': 'plot_1',
                    'type': 'colorer',
                    'target': 'plot_ama',
                    'palette': 'paletteId1',
                  },
                ],
                defaults: {
                  palettes: {
                    paletteId1: {
                      colors: {
                        0: {
                          color: 'red',
                          width: 2,
                          style: 0,
                        },
                        1: {
                          color: 'blue',
                          width: 2,
                          style: 0,
                        },
                      },
                    },
                  },
                  styles: {
                    plot_ama: {
                      linestyle: 0,
                      linewidth: 2,
                      plottype: 0,
                      trackPrice: false,
                      transparency: 0,
                      visible: true,
                      color: '#FFCC33',
                    },
                  },
                  inputs: {
                    N: 10,
                    fast_n: 2,
                    slow_n: 30,
                    cal_type: 0,
                  },
                },
                palettes: {
                  paletteId1: {
                    colors: {
                      0: {
                        name: 'First color',
                      },
                      1: {
                        name: 'Second color',
                      },
                    },
                  },
                },
                styles: {
                  plot_ama: {
                    title: 'AMA',
                    histogramBase: 0,
                  },
                },
                inputs: [
                  {
                    id: 'N',
                    name: 'N',
                    type: 'integer',
                    defval: 10,
                    min: 1,
                    max: 100,
                  },
                  {
                    id: 'fast_n',
                    name: 'Fast n',
                    type: 'integer',
                    defval: 2,
                    min: 1,
                    max: 100,
                  },
                  {
                    id: 'slow_n',
                    name: 'Slow n',
                    type: 'integer',
                    defval: 30,
                    min: 1,
                    max: 100,
                  },
                  {
                    id: 'cal_type',
                    name: '计算方式',
                    type: 'integer',
                    defval: 0,
                    min: 0,
                    max: 1,
                  },
                ],
                format: {},
              },
              constructor: function () {
                this.init = function (context, inputCallback) {
                  context.amas = [];
                };
                this.main = function (context, inputCallback) {
                  this._context = context;
                  this._input = inputCallback;

                  var N = this._input(0);
                  var fast_n = this._input(1);
                  var slow_n = this._input(2);
                  var cal_type = this._input(3);
                  console.log('N', N, 'fast_n', fast_n, 'slow_n', slow_n, 'cal_type', cal_type);

                  const h = this._context.new_var(PineJS.Std.high(this._context));
                  const l = this._context.new_var(PineJS.Std.low(this._context));
                  const c = this._context.new_var(PineJS.Std.close(this._context));

                  // 原始的计算公式
                  let direction = NaN;
                  let volatility = NaN;
                  if (cal_type === 0) {
                    // direction = math.abs(close - close[n])
                    // volatility = math.sum(math.abs(close - close[1]), n)
                    direction = PineJS.Std.abs(c - c.get(N));
                    const close_diff = this._context.new_var(PineJS.Std.abs(c - c.get(1)));
                    volatility = PineJS.Std.sum(close_diff, N, this._context);
                  } else {
                    // 改进后的计算公式
                    // direction = 周期内的最高价 - 最低价
                    // volatility = 周期内的 TR 的和
                    direction = PineJS.Std.highest(h, N, this._context) - PineJS.Std.lowest(l, N, this._context);
                    const trs = this._context.new_var(PineJS.Std.tr(true, this._context));
                    volatility = PineJS.Std.sum(trs, N, this._context);
                  }

                  // ER = direction / volatility
                  const er = direction / volatility;
                  // sc = math.pow(ER * (fastest - slowest) + slowest, 2)
                  const sc = Math.pow(er * (2 / (fast_n + 1) - 2 / (slow_n + 1)) + 2 / (slow_n + 1), 2);

                  if (PineJS.Std.na(sc)) {
                    this._context.amas.push(c.get(0));
                  } else {
                    // ama.get(1) + sc * (c.get(0) - ama.get(1)));
                    this._context.amas.push(this._context.amas[this._context.amas.length - 1] + sc * (c.get(0) - this._context.amas[this._context.amas.length - 1]));
                  }

                  // 颜色设置
                  const colorIndex = (this._context.amas.length >= 2 && this._context.amas[this._context.amas.length - 1] > this._context.amas[this._context.amas.length - 2]) ? 0 : 1;

                  // 返回 this._context.amas 数组最后一个
                  return [this._context.amas[this._context.amas.length - 1], colorIndex];
                };
              }
            },

          ]);
        },
      });

      widget.onChartReady(function () {
        let chart = widget.activeChart();
        chart.onSymbolChanged().subscribe(null, function (symbol) {
          let ticker = symbol["ticker"];
          set_local_data("market", ticker.split(":")[0]);
          set_local_data(
            ticker.split(":")[0] + "_code",
            ticker.split(":")[1]
          );

          if (market !== ticker.split(":")[0]) {
            location.reload();
          }
          market = ticker.split(":")[0];
          code = ticker.split(":")[1];
          console.log(id + " 标的变化：" + symbol["ticker"]);
          clear_draw_chanlun();
          render_zixuan_opts();
          setTimeout(draw_chanlun, 1500);
        });
        chart.onIntervalChanged().subscribe(null, function (interval) {
          set_local_data(market + "_interval_" + id, interval);
          clear_draw_chanlun();
          console.log(id + "周期变化 : " + interval);
          setTimeout(draw_chanlun, 1500);
        });
        chart.onDataLoaded().subscribe(
          null,
          function (d) {
            console.log("数据重新加载");
            clear_draw_chanlun();
            draw_chanlun();
          },
          true
        );
        chart.dataReady(function () {
          console.log("数据准备");
          clear_draw_chanlun();
          draw_chanlun();
        });
        widget.subscribe("onTick", function (d) {
          // Bar 更新，图表也进行更新
          console.log("数据更新");
          draw_chanlun();
        });

        chart
          .onVisibleRangeChanged()
          .subscribe(null, function (visible_range) {
            draw_chanlun();
          });

        // 清除已经画的图
        function clear_draw_chanlun() {
          for (const symbol_key in obj_charts) {
            for (const c_key in obj_charts[symbol_key]) {
              for (const i in obj_charts[symbol_key][c_key]) {
                var o = obj_charts[symbol_key][c_key][i];
                chart.removeEntity(o["id"]);
              }
              obj_charts[symbol_key][c_key] = [];
            }
          }
        }

        // 画图
        function draw_chanlun() {
          const code_start = performance.now();

          let symbolInterval = widget.symbolInterval();
          let bars_result = udf_datafeed._historyProvider.bars_result.get(
            symbolInterval["symbol"].toString().toLowerCase()
          );
          if (bars_result === undefined) {
            return true;
          }
          bars_result = bars_result.get(
            symbolInterval["interval"].toString().toLowerCase()
          );
          // console.log(symbolInterval);
          // console.log(bars_result);

          // 画缠论图表
          let visible_range = chart.getVisibleRange();
          let from = visible_range["from"];
          let to = visible_range["to"];

          let symbol_key =
            symbolInterval["symbol"].toString() +
            "_" +
            symbolInterval["interval"].toString();
          let key_all = [];
          if (obj_charts[symbol_key] === undefined) {
            obj_charts[symbol_key] = {};
          }
          // 删除时间最大的几个画图对象
          for (const c_key of [
            "fxs",
            "bis",
            "xds",
            "zsds",
            "bi_zss",
            "xd_zss",
            "bcs",
            "mmds",
          ]) {
            if (obj_charts[symbol_key][c_key] != undefined) {
              obj_charts[symbol_key][c_key].sort(function (a, b) {
                return a["time"] - b["time"];
              });
              if (obj_charts[symbol_key][c_key].length >= 1) {
                var o = obj_charts[symbol_key][c_key].pop();
                chart.removeEntity(o["id"]);
              }
              if (obj_charts[symbol_key][c_key].length >= 1) {
                var o = obj_charts[symbol_key][c_key].pop();
                chart.removeEntity(o["id"]);
              }
              for (const c_val of obj_charts[symbol_key][c_key]) {
                key_all.push(c_val["key"]);
              }
            } else {
              obj_charts[symbol_key][c_key] = [];
            }
          }

          //** 画分型
          bars_result.fxs.forEach(function (fx) {
            if (fx["points"][0].time >= from) {
              var key = JSON.stringify(fx);
              if (key_all.includes(key)) {
                return true;
              }
              if (fx["text"] === "ding") {
                obj_charts[symbol_key]["fxs"].push({
                  time: fx["points"][0].time,
                  key: key,
                  id: chart.createMultipointShape(fx["points"], {
                    shape: "circle",
                    lock: true,
                    disableSelection: true,
                    disableSave: true,
                    disableUndo: true,
                    showInObjectsTree: false,
                    overrides: {
                      backgroundColor: "#FA8072",
                      color: "#FA8072",
                      linewidth: 4,
                    },
                  }),
                });
              } else {
                obj_charts[symbol_key]["fxs"].push({
                  time: fx["points"][0].time,
                  key: key,
                  id: chart.createMultipointShape(fx["points"], {
                    shape: "circle",
                    lock: true,
                    disableSelection: true,
                    disableSave: true,
                    disableUndo: true,
                    showInObjectsTree: false,
                    overrides: {
                      backgroundColor: "#1E90FF",
                      color: "#1E90FF",
                      linewidth: 4,
                    },
                  }),
                });
              }
            }
          }); //**/
          // 画笔
          bars_result.bis.forEach(function (bi) {
            if (bi["points"][0].time >= from) {
              var key = JSON.stringify(bi);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["bis"].push({
                time: bi["points"][0].time,
                key: key,
                id: chart.createMultipointShape(bi["points"], {
                  shape: "trend_line",
                  lock: true,
                  disableSelection: true,
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  overrides: {
                    linestyle: parseInt(bi["linestyle"]),
                    linewidth: 1,
                    linecolor: "#708090",
                  },
                }),
              });
            }
          });
          // 画线段
          bars_result.xds.forEach(function (xd) {
            if (xd["points"][0].time >= from) {
              var key = JSON.stringify(xd);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["xds"].push({
                time: xd["points"][0].time,
                key: key,
                id: chart.createMultipointShape(xd["points"], {
                  shape: "trend_line",
                  lock: true,
                  disableSelection: true,
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  overrides: {
                    linestyle: parseInt(xd["linestyle"]),
                    linewidth: 2,
                    linecolor: "#00BFFF",
                  },
                }),
              });
            }
          });
          // 画走势段
          bars_result.zsds.forEach(function (zsd) {
            if (zsd["points"][0].time >= from) {
              var key = JSON.stringify(zsd);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["zsds"].push({
                time: zsd["points"][0].time,
                key: key,
                id: chart.createMultipointShape(zsd["points"], {
                  shape: "trend_line",
                  lock: true,
                  disableSelection: true,
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  overrides: {
                    linestyle: parseInt(zsd["linestyle"]),
                    linewidth: 3,
                    linecolor: "#FFA710",
                  },
                }),
              });
            }
          });
          // 画笔中枢
          bars_result.bi_zss.forEach(function (bi_zs) {
            if (bi_zs["points"][0].time >= from) {
              var key = JSON.stringify(bi_zs);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["bi_zss"].push({
                time: bi_zs["points"][0].time,
                key: key,
                id: chart.createMultipointShape(bi_zs["points"], {
                  shape: "rectangle",
                  lock: true,
                  disableSelection: true,
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  overrides: {
                    linestyle: parseInt(bi_zs["linestyle"]),
                    linewidth: 1,
                    linecolor: "#708090",
                    backgroundColor: "#708090",
                    transparency: 95,
                    color: "#708090",
                    "trendline.linecolor": "#708090",
                    fillBackground: true,
                    filled: true,
                  },
                }),
              });
            }
          });
          // 画线段中枢
          bars_result.xd_zss.forEach(function (xd_zs) {
            if (xd_zs["points"][0].time >= from) {
              var key = JSON.stringify(xd_zs);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["xd_zss"].push({
                time: xd_zs["points"][0].time,
                key: key,
                id: chart.createMultipointShape(xd_zs["points"], {
                  shape: "rectangle",
                  lock: true,
                  disableSelection: true,
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  filled: false,
                  overrides: {
                    linestyle: parseInt(xd_zs["linestyle"]),
                    linewidth: 2,
                    color: "#00BFFF",
                    "trendline.linecolor": "#00BFFF",
                    linecolor: "#00BFFF",
                    backgroundColor: "#00BFFF",
                    transparency: 90,
                    fillBackground: true,
                    filled: true,
                  },
                }),
              });
            }
          });
          // 画走势段中枢
          bars_result.zsd_zss.forEach(function (zsd_zs) {
            if (zsd_zs["points"][0].time >= from) {
              var key = JSON.stringify(zsd_zs);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["zsd_zss"].push({
                time: zsd_zs["points"][0].time,
                key: key,
                id: chart.createMultipointShape(zsd_zs["points"], {
                  shape: "rectangle",
                  lock: true,
                  disableSelection: true,
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  overrides: {
                    linestyle: parseInt(zsd_zs["linestyle"]),
                    linewidth: 3,
                    linecolor: "#FFA710",
                    backgroundColor: "#FFA710",
                    transparency: 90,
                    color: "#FFA710",
                    "trendline.linecolor": "#FFA710",
                    fillBackground: true,
                    filled: true,
                  },
                }),
              });
            }
          });
          // 画背驰
          bars_result.bcs.forEach(function (bc) {
            if (bc["points"].time >= from) {
              var key = JSON.stringify(bc);
              if (key_all.includes(key)) {
                return true;
              }
              obj_charts[symbol_key]["bcs"].push({
                time: bc["points"].time,
                key: key,
                id: chart.createShape(bc["points"], {
                  shape: "balloon",
                  lock: true,
                  text: bc["text"],
                  disableSave: true,
                  disableUndo: true,
                  showInObjectsTree: false,
                  overrides: {
                    markerColor: "#D1D4DC",
                    backgroundColor: "#D1D4DC",
                    textColor: "#fccbcd",
                    transparency: 70,
                    backgroundTransparency: 70,
                    fontsize: 12,
                  },
                }),
              });
            }
          });
          // 画买卖点
          bars_result.mmds.forEach(function (mmd) {
            if (mmd["points"].time >= from) {
              var key = JSON.stringify(mmd);
              if (key_all.includes(key)) {
                return true;
              }
              if (mmd["text"].search("B") > 0) {
                obj_charts[symbol_key]["mmds"].push({
                  time: mmd["points"].time,
                  key: key,
                  id: chart.createShape(mmd["points"], {
                    shape: "arrow_up",
                    lock: true,
                    text: mmd["text"],
                    disableSelection: true,
                    disableSave: true,
                    disableUndo: true,
                    showInObjectsTree: false,
                    overrides: {
                      markerColor: "#FA8072",
                      backgroundColor: "#FA8072",
                      color: "#FA8072",
                      fontsize: 12,
                      transparency: 80,
                    },
                  }),
                });
              } else {
                obj_charts[symbol_key]["mmds"].push({
                  time: mmd["points"].time,
                  key: key,
                  id: chart.createShape(mmd["points"], {
                    shape: "arrow_down",
                    lock: true,
                    text: mmd["text"],
                    disableSelection: true,
                    disableSave: true,
                    disableUndo: true,
                    showInObjectsTree: false,
                    overrides: {
                      markerColor: "#1E90FF",
                      backgroundColor: "#1E90FF",
                      color: "#1E90FF",
                      fontsize: 12,
                      transparency: 80,
                    },
                  }),
                });
              }
            }
          });
          const code_end = performance.now();
          console.log(`${symbol_key} 运行时间:${code_end - code_start} 毫秒`);
        }
      });

      return widget;
    }

    function render_zixuan_opts() {
      $.ajax({
        type: "GET",
        url: "/get_stock_zixuan/" + market + "/" + code.replace("/", "__"),
        dataType: "json",
        success: function (res) {
          let data = [];
          layui.each(res, function (i, e) {
            if (e["exists"] === 0) {
              templet =
                '<span><input type="checkbox" /> ' + e["zx_name"] + "</span>";
            } else {
              templet =
                '<span><input type="checkbox" checked /> ' +
                e["zx_name"] +
                "</span>";
            }
            data.push({
              title: e["zx_name"],
              id: i,
              templet: templet,
              exists: e["exists"],
              code: e["code"],
            });
          });
          // 再重新请求一遍自选列表，刷新
          $("#zixuan_groups").change();
          layui.dropdown.reloadData("add_zixuan", {
            data: data,
          });
        },
      });
    }

    // 变更图表展示的标的
    function change_chart_ticker(_market, _code) {
      market = _market;
      code = _code;
      set_local_data("market", market);
      set_local_data(market + "_code", code);
      layui.each(chart_widgets, function (i, w) {
        w.activeChart().setSymbol(market + ":" + code);
      });
      render_zixuan_opts();
    }

    // 更新展示的股票列表涨跌幅
    function stocks_update_rate() {
      // 获取自选列表中的代码
      let codes = [];
      $(".code_rate").each(function () {
        codes.push($(this).data("code"));
      });
      if (codes.length === 0) {
        return true;
      }
      $.ajax({
        type: "POST",
        url: "/ticks",
        data: { market: market, codes: JSON.stringify(codes) },
        dataType: "json",
        success: function (ticks) {
          for (let i = 0; i < ticks["ticks"].length; i++) {
            let tick = ticks["ticks"][i];
            let color = tick["rate"] > 0 ? "#ff5722" : "#16baaa";
            let obj_span_rate = $(
              '.code_rate[data-code="' + tick["code"] + '"]'
            );
            obj_span_rate.html(tick["rate"] + "%");
            obj_span_rate.css("color", color);
          }
          let now_trading = ticks["now_trading"];
          if (now_trading !== true) {
            clearInterval(interval_update_rates);
          }
        },
      });
    }

    // 自选列表渲染与操作
    function render_zixuan_stocks() {
      layui.use(["table", "dropdown", "util"], function () {
        let table = layui.table;
        let dropdown = layui.dropdown;
        // 创建自选列表渲染实例
        table.render({
          elem: "#table_zixuan_list",
          defaultContextmenu: false,
          url: "/get_zixuan_stocks/" + market + "/" + zx_group,
          page: false,
          className: "layui-font-12",
          size: "sm",
          cols: [
            [
              {
                field: "code",
                title: "代码",
                sort: false,
                templet: function (d) {
                  if (d.color !== undefined && d.color !== "") {
                    return (
                      '<span style="color: ' +
                      d.color +
                      '">' +
                      d.code +
                      "</span>"
                    );
                  }
                  return d.code;
                },
              },
              {
                field: "name",
                title: "名称",
                sort: false,
                templet: function (d) {
                  if (d.color !== undefined && d.color !== "") {
                    return (
                      '<span style="color: ' +
                      d.color +
                      '">' +
                      d.name +
                      "</span>"
                    );
                  }
                  return d.name;
                },
              },
              {
                field: "zf",
                title: "涨跌幅",
                sort: false,
                width: 70,
                templet: function (d) {
                  return (
                    '<span class="code_rate" data-code="' +
                    d.code +
                    '">--</span>'
                  );
                },
              },
            ],
          ],
          done: function () {
            stocks_update_rate();
          },
        });
        // 行单击事件( 双击事件为: rowDouble )
        table.on("row(table_zixuan_list)", function (obj) {
          let data = obj.data; // 获取当前行数据
          code = data.code;
          change_chart_ticker(market, code);
          $('#ai_code').val(code);
          table.setRowChecked("table_zixuan_list", {
            index: "all", // 所有行
            checked: false,
          });
          table.setRowChecked("table_zixuan_list", {
            index: obj.index, // 选中行的下标。 0 表示第一行
          });
        });
        // 右键菜单
        table.on("rowContextmenu(table_zixuan_list)", function (obj) {
          let data = obj.data; // 获取当前行数据
          // 右键操作
          let menu_data = [
            { title: "删除", id: "del" },
            { title: "置顶", id: "sort_1", direction: "top" },
            { title: "置底", id: "sort_2", direction: "bottom" },
            {
              title: "色彩",
              id: "color_1",
              color: "#ff5722",
              templet: function () {
                return '<div class="layui-bg-red">红色</div>';
              },
            },
            {
              title: "色彩",
              id: "color_2",
              color: "#ffb800",
              templet: function () {
                return '<div class="layui-bg-orange">橙色</div>';
              },
            },
            {
              title: "色彩",
              id: "color_3",
              color: "#16baaa",
              templet: function () {
                return '<div class="layui-bg-green">绿色</div>';
              },
            },
            {
              title: "色彩",
              id: "color_4",
              color: "#1e9fff",
              templet: function () {
                return '<div class="layui-bg-blue">蓝色</div>';
              },
            },
            {
              title: "色彩",
              id: "color_5",
              color: "#a233c6",
              templet: function () {
                return '<div class="layui-bg-purple">紫色</div>';
              },
            },
            {
              title: "色彩",
              id: "color_6",
              color: "",
              templet: function () {
                return '<div class="layui-bg-gray">清除颜色</div>';
              },
            },
          ];
          if (market === "a") {
            menu_data.splice(3, 0, { title: "操盘必读", id: "dfcf" });
          }
          dropdown.render({
            trigger: "contextmenu",
            show: true,
            data: menu_data,
            click: function (menuData, othis) {
              if (menuData["id"] === "del") {
                $.ajax({
                  type: "POST",
                  url: "/set_stock_zixuan",
                  data: {
                    opt: "DEL",
                    market: market,
                    group_name: zx_group,
                    code: data.code,
                    color: "",
                    direction: "",
                  },
                  dataType: "json",
                  success: function (res) {
                    if (res["ok"]) {
                      layer.msg("删除成功");
                    } else {
                      layer.msg("删除失败");
                    }
                    // 再重新请求一遍自选列表，刷新
                    render_zixuan_stocks();
                  },
                });
              } else if (menuData["title"] === "色彩") {
                $.ajax({
                  type: "POST",
                  url: "/set_stock_zixuan",
                  data: {
                    opt: "COLOR",
                    market: market,
                    group_name: zx_group,
                    code: data.code,
                    color: menuData["color"],
                    direction: "",
                  },
                  dataType: "json",
                  success: function (res) {
                    // 再重新请求一遍自选列表，刷新
                    render_zixuan_stocks();
                  },
                });
              } else if (
                menuData["id"] === "sort_1" ||
                menuData["id"] === "sort_2"
              ) {
                $.ajax({
                  type: "POST",
                  url: "/set_stock_zixuan",
                  data: {
                    opt: "SORT",
                    market: market,
                    group_name: zx_group,
                    code: data.code,
                    color: "",
                    direction: menuData["direction"],
                  },
                  dataType: "json",
                  success: function (res) {
                    // 再重新请求一遍自选列表，刷新
                    render_zixuan_stocks();
                  },
                });
              } else if (menuData["id"] === "dfcf") {
                window.open(
                  "https://emweb.securities.eastmoney.com/pc_hsf10/pages/index.html?type=web&code=" +
                  data.code.replace(".", "")
                );
              }
            },
          });
        });
      });
    }

    // 获取警报记录
    function get_alert_records() {
      layui.use(["table"], function () {
        let table = layui.table;

        // 创建自选列表渲染实例
        table.render({
          elem: "#table_alert_reocrds",
          defaultContextmenu: false,
          url: "/alert_records/" + market,
          page: false,
          className: "layui-font-12",
          size: "sm",
          maxHeight: 550,
          cols: [
            [
              { field: "jh_type", title: "触发", sort: false, width: 230 },
              {
                field: "datetime_str",
                title: "时间",
                sort: false,
                width: 160,
              },
              { field: "code", title: "代码", sort: false, width: 80 },
              { field: "name", title: "名称", sort: false, width: 80 },
              { field: "frequency", title: "周期", sort: false, width: 60 },
              {
                field: "task_name",
                title: "监控名称",
                sort: false,
                width: 80,
                templet: function (d) {
                  return d.task_name;
                },
              },
              { field: "is_done", title: "是否完成", sort: false, width: 80 },
              { field: "is_td", title: "是否停顿", sort: false, width: 80 },
            ],
          ],
        });
        // 单击警报内容列表
        table.on("row(table_alert_reocrds)", function (obj) {
          let data = obj.data; // 获取当前行数据
          change_chart_ticker(market, data.code);
        });
      });
    }

    // 获取AI分析记录
    function get_ai_analyse_records() {
      function formatStringWithWhitespace(str) {
        // 将 \n 替换为 <br>
        str = str.replace(/\n/g, '<br>');
        // 将 \t 替换为 &nbsp;&nbsp;&nbsp;&nbsp;（4 个空格）
        str = str.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
        return str;
      }
      layui.use(["table"], function () {
        let table = layui.table;

        // 创建自选列表渲染实例
        table.render({
          elem: "#table_ai_analysis",
          defaultContextmenu: false,
          url: "/ai/analyse_records/" + market,
          page: false,
          className: "layui-font-12",
          size: "sm",
          maxHeight: 750,
          cols: [
            [
              { field: "stock_name", title: "名称", sort: false, width: 100 },
              { field: "stock_code", title: "代码", sort: false, width: 80 },
              { field: "frequency", title: "周期", sort: false, width: 60 },
              { field: "dt", title: "时间", sort: false, width: 160 },
            ],
          ],
        });
        // 单击警报内容列表
        table.on("row(table_ai_analysis)", function (obj) {
          let data = obj.data; // 获取当前行数据
          var title = "AI分析 " + data.stock_code + " " + data.stock_name + " " + data.frequency + data.dt + " 模型 " + data.model;
          var show_html = "<ul class='layui-padding-2 layui-text layui-font-black'><li><h3>缠论状态:</h3></li><li><p>" + formatStringWithWhitespace(data.prompt) + "</p></li><li><h3>AI分析：</h3></li><li><p>" + formatStringWithWhitespace(data.msg) + "</p></li></ul>";
          layer.open({
            type: 1,
            title: title,
            content: show_html,
            area: ['720px', '950px'],
            // maxHeight: 950,
            anim: 'slideLeft',
            shade: 0,
          });

          // 切换到该股票
          change_chart_ticker(market, data.stock_code);
          $('#ai_code').val(data.stock_code);
        });
      });
    }

    // 缠论配置项更改
    layui.use(function () {
      var layer = layui.layer;
      var element = layui.element;
      var form = layui.form;
      var util = layui.util;
      var dropdown = layui.dropdown;

      // 下拉菜单事件
      dropdown.render({
        elem: "#toggle-menu",
        data: [
          {
            title: "缠论配置项",
            templet:
              '<div><i class="layui-icon layui-icon-set"></i> <span>缠论配置项</span></div>',
            id: 1,
          }, {
            title: "自选组管理",
            templet:
              '<div><i class="layui-icon layui-icon-templeate-1"></i> <span>自选组管理</span></div>',
            id: 2,
          }, {
            title: "监控列表",
            templet:
              '<div><i class="layui-icon layui-icon-list"></i> <span>监控列表</span></div>',
            id: 3,
          }, {
            title: "新增监控",
            templet:
              '<div><i class="layui-icon layui-icon-speaker"></i> <span>新增监控</span></div>',
            id: 4,
            // }, {
            //   title: "运行选股",
            //   templet:
            //     '<div><i class="layui-icon layui-icon-test"></i> <span>运行选股</span></div>',
            //   id: 5,
          }, {
            title: "当前任务",
            templet:
              '<div><i class="layui-icon layui-icon-senior"></i> <span>当前任务</span></div>',
            id: 6,
          }, {
            title: "切换单/双图",
            templet:
              '<div><i class="layui-icon layui-icon-carousel"></i> <span>切换单/双图</span></div>',
            id: 7,
          }, {
            title: "切换主题",
            templet:
              '<div><i class="layui-icon layui-icon-engine"></i> <span>切换主题</span></div>',
            id: 8,
          },
          // {
          //   title: "系统设置",
          //   templet:
          //     '<div><i class="layui-icon layui-icon-set"></i> <span>系统设置</span></div>',
          //   id: 9,
          // },
        ],
        click: function (item) {
          if (item.title === "缠论配置项") {
            layer.open({
              type: 2,
              title: "缠论配置项变更",
              area: ["1100px", "90vh"],
              content:
                "/get_cl_config/" + market + "/" + code.replace("/", "__"),
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
            });
          } else if (item.title === "自选组管理") {
            layer.open({
              type: 2,
              title: "自选组管理",
              area: ["800px", "50vh"],
              content: "/zixuan_group/" + market,
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
            });
          } else if (item.title === "监控列表") {
            layer.open({
              type: 1,
              title: "监控列表",
              area: ["1000px", "50vh"],
              content: $("#layer-wrapper-alert-task-list"),
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
              btn: [],
              success: function (layero, index, that) {
                layui.use(["table", "dropdown", "util"], function () {
                  let table = layui.table;
                  let dropdown = layui.dropdown;
                  // 创建自选列表渲染实例
                  table.render({
                    elem: "#table_alerts",
                    defaultContextmenu: false,
                    url: "/alert_list/" + market,
                    page: false,
                    className: "layui-font-12",
                    size: "sm",
                    cols: [
                      [
                        { field: "task_name", title: "监控名称" },
                        {
                          field: "zx_group",
                          title: "自选组",
                          templet: function (d) {
                            return d.zx_group;
                          },
                        },
                        {
                          filed: "frequency",
                          title: "周期",
                          templet: function (d) {
                            return d.frequency;
                          },
                        },
                        {
                          filed: "interval_minutes",
                          title: "运行间隔(分钟)",
                          sort: true,
                          templet: function (d) {
                            return d.interval_minutes;
                          },
                        },
                        {
                          filed: "check_bi_type",
                          title: "笔方向",
                          templet: function (d) {
                            return d.check_bi_type;
                          },
                        },
                        {
                          filed: "check_bi_beichi",
                          title: "笔背驰",
                          templet: function (d) {
                            return d.check_bi_beichi;
                          },
                        },
                        {
                          filed: "check_bi_mmd",
                          title: "笔买卖点",
                          templet: function (d) {
                            return d.check_bi_mmd;
                          },
                        },
                        {
                          filed: "check_xd_type",
                          title: "线段方向",
                          templet: function (d) {
                            return d.check_xd_type;
                          },
                        },
                        {
                          filed: "check_xd_beichi",
                          title: "线段背驰",
                          templet: function (d) {
                            return d.check_xd_beichi;
                          },
                        },
                        {
                          filed: "check_xd_mmd",
                          title: "线段买卖点",
                          templet: function (d) {
                            return d.check_xd_mmd;
                          },
                        },
                        {
                          filed: "is_send_msg",
                          title: "发送消息",
                          sort: true,
                          templet: function (d) {
                            if (d.is_send_msg === 1) {
                              return "发送";
                            } else {
                              return "不发";
                            }
                          },
                        },
                        {
                          filed: "is_run",
                          title: "启用",
                          sort: true,
                          templet: function (d) {
                            if (d.is_run === 1) {
                              return "启用";
                            } else {
                              return "禁用";
                            }
                          },
                        },
                      ],
                    ],
                  });
                  // 行双击事件( 双击事件为: rowDouble )
                  table.on("row(table_alerts)", function (obj) {
                    let data = obj.data; // 获取当前行数据
                    layer.open({
                      type: 2,
                      title: "修改警报提醒",
                      area: ["1000px", "90vh"],
                      content: "/alert_edit/" + market + "/" + data.id,
                      anim: 1,
                      fixed: true, // 不固定
                      shadeClose: true,
                    });
                  });
                  // 右键菜单
                  table.on("rowContextmenu(table_alerts)", function (obj) {
                    let data = obj.data; // 获取当前行数据
                    // 右键操作
                    dropdown.render({
                      trigger: "contextmenu",
                      show: true,
                      data: [{ title: "删除", id: "del" }],
                      click: function (menuData, othis) {
                        if (menuData["id"] === "del") {
                          $.ajax({
                            type: "GET",
                            url: "/alert_del/" + data.id,
                            dataType: "json",
                            success: function (res) {
                              if (res["ok"]) {
                                layer.msg("删除成功");
                              } else {
                                layer.msg("删除失败");
                              }
                              get_alerts();
                            },
                          });
                        }
                      },
                    });
                  });
                });
              },
              end: function () {
                $("#layer-wrapper-alert-task-list").hide();
              },
            });
          } else if (item.title === "新增监控") {
            layer.open({
              type: 2,
              title: "新增监控提醒",
              area: ["1000px", "90vh"],
              content: "/alert_edit/" + market + "/0",
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
            });
          } else if (item.title === "运行选股") {
            layer.open({
              type: 2,
              title: "运行选股",
              area: ["1000px", "50vh"],
              content: "/xuangu/task_list/" + market,
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
            });
          } else if (item.title === "当前任务") {
            layer.open({
              type: 2,
              title: "当前任务",
              area: ["1000px", "50vh"],
              content: "/jobs",
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
            });
          } else if (item.title === "切换单/双图") {
            let chart_num = get_local_data("chart_num");
            if (chart_num === "1") {
              set_local_data("chart_num", "2");
            } else {
              set_local_data("chart_num", "1");
            }
            location.reload();
          } else if (item.title === "切换主题") {
            let theme = get_local_data("theme");
            if (theme === "Light") {
              set_local_data("theme", "dark");
            } else {
              set_local_data("theme", "Light");
            }
            location.reload();
          } else if (item.title === "系统设置") {
            layer.open({
              type: 2,
              title: "系统设置",
              area: ["1000px", "50vh"],
              content: "/setting",
              anim: 1,
              fixed: true, // 不固定
              shadeClose: true,
            });
          }
        },
      });

      // 自选操作下拉菜单
      dropdown.render({
        elem: "#add_zixuan",
        data: [],
        click: function (data, othis) {
          let opt = "ADD";
          if (data["exists"] === 1) {
            opt = "DEL";
          }
          $.ajax({
            type: "POST",
            url: "/set_stock_zixuan",
            data: {
              opt: opt,
              market: market,
              group_name: data["title"],
              code: data["code"],
              color: "",
              direction: "",
            },
            dataType: "json",
            success: function (res) {
              render_zixuan_opts();
              render_zixuan_stocks();
            },
          });
          return false;
        },
      });

      // 折叠面板事件
      element.on("collapse(collapse-opts)", function (data) {
        let is_open = data.show; // 得到当前面板的展开状态，true or false
        let ca_title = $(data.title).attr("data-ca-title"); // 得到当前点击面板的标题区域对象

        if (ca_title === "自选组") {
          if (is_open) {
            interval_update_rates = setInterval(stocks_update_rate, 30000);
          } else {
            clearInterval(interval_update_rates);
          }
        }
        if (ca_title === "监控提醒") {
          if (is_open) {
            // 获取警报记录
            get_alert_records();
            interval_get_alert_records = setInterval(
              get_alert_records,
              60000
            );
          } else {
            clearInterval(interval_get_alert_records);
          }
        }
      });

      // 初始赋值
      form.val("select_market_form", {
        market: market,
      });
      // 表单变动
      form.on("select(select_market)", function (data) {
        market = data.value;
        set_local_data("market", market);
        location.reload();
      });
      // 自选组变化
      form.on("select(select_zx_group)", function (data) {
        zx_group = data.value;
        render_zixuan_stocks();
      });

      // 固定条，显示与隐藏菜单栏
      util.fixbar({
        bars: [{ // 定义可显示的 bar 列表信息 -- v2.8.0 新增
          type: 'hide_menu',
          icon: 'layui-icon-spread-left' // layui-icon-shrink-right
        }],
        default: false,
        on: { // 任意事件 --  v2.8.0 新增

        },
        // 点击事件
        click: function (type) {
          if (type === "hide_menu") {
            var fixed_li = $('.layui-fixbar  li:first-child')
            if (fixed_li.attr('class').includes('layui-icon-spread-left')) {
              fixed_li.removeClass('layui-icon-spread-left').addClass('layui-icon-shrink-right');
              $('#chart_menu').hide();
              $('#chart_container').removeClass('layui-col-xs10 layui-col-sm10 layui-col-md10').addClass('layui-col-xs12 layui-col-sm12 layui-col-md12');
            } else {
              fixed_li.removeClass('layui-icon-shrink-right').addClass('layui-icon-spread-left');
              $('#chart_menu').show();
              $('#chart_container').removeClass('layui-col-xs12 layui-col-sm12 layui-col-md12').addClass('layui-col-xs10 layui-col-sm10 layui-col-md10');
            }
          }
        },
      });

      // AI 分析助手
      let ai_frequencys = $("#ai_frequencys");
      $(ai_frequencys).html();
      layui.each(market_frequencys[market], function (i, f) {
        $(ai_frequencys).append(
          "<option value='" + f + "'>" + f + "</option>"
        );
      });
      layui.form.render($(ai_frequencys));
      $(ai_frequencys)
        .siblings("div.layui-form-select")
        .find("dl")
        .find("dd[lay-value=d]")
        .click();

      $('#ai_code').val(code);

      get_ai_analyse_records();

      $('#ai_analyse_btn').click(function () {
        // 将 btn 置灰
        $('#ai_analyse_btn').addClass('layui-btn-disabled').attr('disabled', true);
        $('#ai_analyse_btn').html('分析中...');
        $.ajax({
          type: "POST",
          url: "/ai/analyse",
          data: {
            market: market,
            code: $('#ai_code').val(),
            frequency: $('#ai_frequencys').val(),
          },
          dataType: "json",
          success: function (res) {
            if (res["ok"] === true) {
              layer.msg("分析成功");
              get_ai_analyse_records();
            } else {
              layer.msg(res["msg"]);
            }
            $('#ai_analyse_btn').removeClass('layui-btn-disabled').attr('disabled', false);
            $('#ai_analyse_btn').html('分析');
          }
        })
      });

    });

    $("#refresh_zixuan").click(function () {
      render_zixuan_stocks();
    });

    // 代码搜索
    const searchSelect = xmSelect.render({
      el: "#code_search",
      filterable: true,
      remoteSearch: true,
      radio: true,
      clickClose: true,
      tips: "商品代码搜索",
      empty: "没有搜索商品",
      theme: {
        color: '#e54d42',
        // hover: '#e54d42',
      },
      delay: 1000,
      remoteMethod: function (val, cb, show) {
        if (val) {
          $.ajax({
            type: "GET",
            url:
              "/tv/search?limit=30&type=&query=" + val + "&exchange=" + market,
            dataType: "json",
            success: function (res) {
              let lst = [];
              layui.each(res, function (i, r) {
                lst.push({
                  name: r["symbol"] + ":" + r["description"],
                  value: r["symbol"],
                });
              });
              cb(lst);
            },
          });
        } else {
          // 远程搜索时，文本框内物搜索关键字，使用缓存的历史记录展示
          let storedItems = JSON.parse(localStorage.getItem(market + '_selectedItems')) || [];
          cb(storedItems)
        }
      },
      show: function () {
        // 展开折叠面板加载历史记录
        let storedItems = JSON.parse(localStorage.getItem(market + '_selectedItems')) || [];
        searchSelect.update({
          data: storedItems
        });
      },
      on: function (data) {
        if (data.arr.length > 0) {
          change_chart_ticker(market, data.arr[0]["value"]);
          add_to_cache(data);
        }
      },
      data: [],
    });

    function add_to_cache(data) {
      // 获取之前的列表
      let selectedItems = JSON.parse(localStorage.getItem(market + '_selectedItems')) || [];

      // 将当前选择的项目添加到列表的最前面
      selectedItems.unshift({
        name: data.arr[0].name,
        value: data.arr[0].value
      });

      // 只保留最近的30个
      selectedItems = selectedItems.slice(0, 30);

      // 在最后放到缓存之前去重，保留最近的项
      const uniqueItems = [];
      const seenValues = new Set();

      for (const item of selectedItems) {
        if (!seenValues.has(item.value)) {
          seenValues.add(item.value);
          uniqueItems.push(item);
        }
      }

      // 更新 localStorage
      localStorage.setItem(market + '_selectedItems', JSON.stringify(uniqueItems));
    }

    // 初始化展示页面
    function init_web() {
      // 获取自选组
      $.ajax({
        type: "GET",
        url: "/get_zixuan_groups/" + market,
        dataType: "json",
        success: function (res) {
          let zixuan_groups = $("#zixuan_groups");
          $(zixuan_groups).html();
          layui.each(res, function (i, r) {
            $(zixuan_groups).append(
              "<option value='" + r.name + "'>" + r.name + "</option>"
            );
          });
          layui.form.render($(zixuan_groups));
          $(zixuan_groups)
            .siblings("div.layui-form-select")
            .find("dl")
            .find("dd")[0]
            .click();
        },
      });

      let chart_num = get_local_data("chart_num");
      let tv_chart_container_1 = $("#tv_chart_container_1");
      let tv_chart_container_2 = $("#tv_chart_container_2");
      let win_height = window.innerHeight;
      if (chart_num === "1") {
        tv_chart_container_1.css("flex", "1");
        tv_chart_container_2.css("flex", "0");
        chart_widgets.push(show_tv_chart("1", win_height));
      } else {
        chart_widgets.push(show_tv_chart("1", win_height / 2));
        chart_widgets.push(show_tv_chart("2", win_height / 2));
      }

      render_zixuan_opts();
    }

    init_web();
    interval_update_rates = setInterval(stocks_update_rate, 30000);


  });
</script>

</html>